﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel.Statements
{
    public class ForEachStatement : Statement
    {
        public Operator Content;
        public VarStatement Var;
        public StatementList Statements;
        /// <summary>
        /// If true, the foreach loop iterates over an iterator, i.e. the Content is an iterator function.
        /// </summary>
        public bool UsesAnIterator;

        public override void ComputeType(ErrorReport report)
        {
            // Store the data type we are iterating over. This can either
            // be a list or an iterator function.
            ITypeDef t = null;
            // The data type of the variable we are assigning the items to. May be null.
            ITypeDef vt = null;
            // The type of the counter variable is specified?
            if (this.Var.Type != null)
            {
                if (!(this.Var.Type is InstanceType))
                    return;
                vt = (this.Var.Type as InstanceType).Type;
                ITypeDef goal = ListType.Instantiate(vt);
                t = this.Content.ComputeType(goal, false, report);
            }
            // Counter variable type is not specified
            else
            {
                t = this.Content.ComputeType(null, false, report);
            }
            // Some error?
            if (t == null)
                return;

            bool isinstance = false;
            if (t is InstanceType)
            {
                t = (t as InstanceType).Type;
                isinstance = true;
            }

            // Iterating over an iterator function?
            if (t is FunctionTypeDef || t is FunctionSetTypeDef)
            {
                this.UsesAnIterator = true;
                FunctionTypeDef func = null;
                if (t is FunctionSetTypeDef)
                {
                    func = (t as FunctionSetTypeDef).FindFunction(new List<ITypeDef>(), report, this.Node);

                    if (this.Content is AtomIdentifier)
                        (this.Content as AtomIdentifier).Object = func;
                    else if (this.Content is PropertyOperator)
                        (this.Content as PropertyOperator).Object = func as MemberFunction;
                }
                else
                {
                    func = t as FunctionTypeDef;
                    if (func.ParameterTypes.Count != 0)
                    {
                        report.Add(new ErrorEntry(this.Content.Node) { Type = ErrorType.NotAnIterator });
                        return;
                    }
                }
                // Some error?
                if (func == null)
                    return;

                // Ensure it is an instance (especially if a function has been passed)
                if ( !isinstance && this.ToInstance(ref this.Content, func, report) == null )
                    return;

                // Return type is void -> not an iterator
                if ( func.ReturnType is VoidType )
                {
                    report.Add(new ErrorEntry(this.Content.Node) { Type = ErrorType.NotAnIterator });
                    return;
                }
                // Does the return type match
                if (this.Var.Type != null && !t.CanBeAssignedTo(this.Var.Type))
                {
                    report.Add(new ErrorEntry(this.Node) { Type = ErrorType.IncompatibleTypes });
                    return;
                }
                else if (this.Var.Type == null)
                {
                    this.Var.Type = func.ReturnType;
                }
                return;
            }

            if (!isinstance)
            {
                this.ReportNotAnObject(this.Content.Node, report);
                return;
            }

            // Iterating over a list?
            if ( !(t is TemplateInstanceTypeDef) || (t as TemplateInstanceTypeDef).Template != ListType.Self )
            {
                report.Add(new ErrorEntry(this.Content.Node) { Type = ErrorType.NotAList });
                return;
            }
            ITypeDef targ = (t as TemplateInstanceTypeDef).TemplateArguments[ListType.Self.TemplateParameters[0]];
            if (this.Var.Type == null)
                this.Var.Type = new InstanceType() { Type = targ };
            else
            {
                if (!targ.CanBeAssignedTo((this.Var.Type as InstanceType).Type))
                {
                    report.Add(new ErrorEntry(this.Node) { Type = ErrorType.IncompatibleTypes });
                    return;
                }
            }
        }
    }
}
